import {
  RepeatWrapping,
  Euler,
  Group,
  Mesh,
  MeshPhongMaterial,
  Quaternion,
  BoxGeometry,
  TextureLoader,
  Vector3,
  Color,
  AxesHelper,
  CylinderGeometry,
  SphereGeometry,
  SphereBufferGeometry,
  ConeGeometry
} from "./3rdParty/three.js/build/three.module.js";

import { worldTransform, toAmmoType } from "./physics/AmmoHelper.js";
import { generateUUID, isNumber, mtlFromJSON, mtlToJSON } from "./common/Common.js";
import A3ObjectTypes from "./A3ObjectTypes.js";

/**
 * A3对象
 */
class A3Object {

  type=A3ObjectTypes.A3Object;
  name=A3ObjectTypes.A3Object;

  isA3Object = true;
  isA3ObjectGroup = false;

  // 位姿信息
  position = new Vector3( 0, 0, 0 );
  quaternion = new Quaternion( 0, 0, 0, 1 );

  // 用户位姿
  hasUserTransform = false;
  userPosition = new Vector3( 0, 0, 0 );
  userQuaternion = new Quaternion( 0, 0, 0, 1 );

  // 物理参数
  mass = 1.0;
  friction = 0.5;
  restitution = 0.0;

  disableGravity = false;

  inertia = new Vector3( 0, 0, 0 );
  linearVelocity = new Vector3( 0, 0, 0 );
  angularVelocity = new Vector3( 0, 0, 0 );

  // 模型参数
  visual = undefined;
  collider = undefined;
  threeObject = new Group();
  physicsShape = undefined;
  physicsBody = undefined;

  // 约束条件
  constraints = [];

  // 几何参数
  width = 1.0;    // x
  height = 1.0;   // y
  depth = 1.0;    // z
  meshType = "box";

  // 材质参数
  color = new Color( .129, .5, .82 ); 
  material = undefined;
  materials = [];

  textureLoader = new TextureLoader();
  // 碰撞模型材质
  colliderMaterial = new MeshPhongMaterial({ 
    color: 0xff0000,
    wireframe: true,
  });

  // 仿真控制
  recycleable = false;
  isPhysical = true;    // 假时表示该物体仅做3D显示用，无物理实体；

  // 辅助工具
  axesHelper = undefined;

  // 动作相关
  /**
   * 格式:
   * name: 动作名
   * shortcut: 快捷键
   * action: 动作
   * enable: 有效标识
   */
  actionList = [];
  actionMap = new Map();

  constructor( width, height, depth, meshType="box", constructOnly=false ) {

    width = isNumber( width ) ? width : 1;
    height = isNumber( height ) ? height : 1;
    depth = isNumber( depth ) ? depth : 1;

    this.width = width;
    this.height = height;
    this.depth = depth;
    this.meshType = meshType;
    this.uuid = generateUUID();

    if( constructOnly ) return;

    this.init();

    this.initActions();

    // this.enalbeAxes();
    this.enableShadow();

  }

/******* ******* ******* ******* ******* ******* *******/
/******* 初始化 *******/
/******* ******* ******* ******* ******* ******* *******/
  init( ...args ) {
    
    if( args.length === 0 ) {
      args[0] = this.width;
      args[1] = this.height;
      args[2] = this.depth;
    }

    const meshType = this.meshType;
    let mesh;

    switch( meshType ) {
      case "cylinder":
        mesh = new Mesh( new CylinderGeometry( args[0], args[1], args[2] ) );
        break;
      case "cone":
        mesh = new Mesh( new ConeGeometry( args[0], args[1] ) );
        break;
      case "sphere":
        mesh = new Mesh( new SphereGeometry( args[0] ) );
        break;
      case "box":
      default:
        mesh = new Mesh( new BoxGeometry( args[0], args[1], args[2] ) );
        break;
    }

    this.visual = mesh;
    this.visual.visible = true;
    this.collider = mesh.clone();
    this.collider.material = this.colliderMaterial;
    this.collider.visible = false;

    this.threeObject.add(this.visual);
    this.threeObject.add(this.collider);

    this.position.set( 0, 0, 0 );
    this.threeObject.position.copy( this.position );

    this.initMaterials();
    // 几何为球体时，多个材质会导致球体显示异常？
    this.visual.material = (meshType !== "sphere") ? this.materials : this.material;

  }

  initActions() {
    this.addAction( "debug", "Backquote", this.debugAction.bind(this) );
  }

/******* ******* ******* ******* ******* ******* *******/
/******* 状态设置与获取 *******/
/******* ******* ******* ******* ******* ******* *******/

  setColor( r, g, b ) {

    if( this.material ) {
      this.material.color.setRGB( r, g, b );
      this.material.needsUpdate = true;
    }

    for( var i = 0, il = this.materials.length; i < il; i ++ ) {
      this.materials[i].color.setRGB( r, g, b );
      this.materials[i].needsUpdate = true;
    }  
    
    this.color.setRGB( r, g, b );
  
  }

  setOpacity( opacity=-1 ) {
    
    // if( this.material ) {
    //   this.material.transparent = opacity < 0 ? false : true ;
    //   this.material.opacity = opacity;
    //   this.material.needsUpdate = true;
    // }

    for( var i = 0, il = this.materials.length;  i < il; i++ ){
      const material = this.materials[i];
      material.transparent = opacity < 0 ? false : true ;
      material.opacity = opacity;
      material.needsUpdate = true;
    }

  }

  initMaterials() {

    const material = new MeshPhongMaterial({ color: this.color });
    for( var i = 0; i < 6; i++ ){
      this.materials.push( material );
    }

    this.material = material;

  }

  setPosition( x, y, z ) {
    
    this.position.set( x, y, z );
    this.updateWorldTransform();

  }

  setRotation( rx, ry, rz ) {

    this.quaternion.setFromEuler( new Euler( rx, ry, rz ) );
    this.updateWorldTransform();

  }

  setUserPosition( x, y, z ) {

    this.userPosition.set( x, y, z );
    this.updateUserTransform();

  }

  setUserRotation( rx, ry, rz ) {

    this.userQuaternion.setFromEuler( new Euler( rx, ry, rz ) );
    this.updateUserTransform();

  }

  setRestitution( restitution ){

    if( isNaN(restitution) ) {
      this.restitution = restitution;
    }

  }

  setFriction( friction ) {

    if( isNaN(restitution) ) {
      this.friction = friction;
    }

  }

  updateWorldTransform( position, quaternion ) {

    if( !position ) {
      position = this.position;
    }

    if( !quaternion ) {
      quaternion = this.quaternion;
    }

    if ( this.threeObject ) {

      this.threeObject.position.copy( position );
      this.threeObject.quaternion.copy( quaternion );

    }
 
    if( this.physicsBody ) {
      
      const ms = this.physicsBody.getMotionState();
      ms.getWorldTransform( worldTransform );
      worldTransform.setOrigin( toAmmoType( position ) );
      worldTransform.setRotation( toAmmoType( quaternion ) );
      this.physicsBody.setWorldTransform( worldTransform );
      
    }

    this.position.copy( position );
    this.quaternion.copy( quaternion );

  }

  updateUserTransform ( position, quaternion ) {
    
    if( !position ) {
      position = this.userPosition;
    }

    if( !quaternion ) {
      quaternion = this.userQuaternion;
    }
    
    if( this.visual ) {
      this.visual.position.copy( position );
      this.visual.quaternion.copy( quaternion );
    }

    if( this.collider ) {
      this.collider.position.copy( position );
      this.collider.quaternion.copy( quaternion );
    }

    if( this.physicsShape ) {
      this.physicsShape.updateChild( 0, position, quaternion );
    }

    this.userPosition.copy( position );
    this.userQuaternion.copy( quaternion );

  }

  setLinearVelocity( lv ) {

    if( this.physicsBody ) {
      if( this.isA3ObjectGroup && this.enableBaseShape ){
        this.physicsBody[0].setLinearVelocity( toAmmoType( lv ) );
      }
      else {
        this.physicsBody.setLinearVelocity( toAmmoType( lv ) );
      }
    }

  }

  setAngularVelocity( av ) {

    if( this.physicsBody ) {

      if( this.isA3ObjectGroup && this.enableBaseShape ){
        this.physicsBody[0].setAngularVelocity( toAmmoType( av ) );
      }
      else {
        this.physicsBody.setAngularVelocity( toAmmoType( av ));
      }
      
    }

  }

  setTexture( url, index = -1, config ) {
   
    this.textureLoader.load( url, (texture)=>{

      if( config ){
        if( config.repeat ){
          texture.wrapS = RepeatWrapping;
          texture.wrapT = RepeatWrapping;
          texture.repeat.set( config.repeat[0], config.repeat[1] );
        }

        if( config.offset ){
          texture.offset.set( config.offset[0], config.offset[1] );
        }

        if( config.center ){
          texture.center.set( config.center[0], config.center[1] );
        }

        if( config.rotation ){
          texture.rotation = config.rotation;
        }
      }

      // console.log( texture );

      if( index < 0 ) {

        // this.material.map = texture;
        // this.material.userData.texture = url;
        // this.material.needsUpdate = true;

        for( var i = 0, il = this.materials.length; i < il; i ++ ) {
          this.materials[i].map = texture;
          this.materials[i].userData.texture = url;
          this.materials[i].needsUpdate = true;
        }

      }
      else if( index < this.materials.length ){

        if( this.materials[index] === this.material ) {
          this.materials[index] = this.material.clone();
        }

        this.materials[index].map = texture;
        this.materials[index].userData.texture = url;
        this.materials[index].needsUpdate = true;

      }

    });

  }

  switchView( enableVisual ) {

    if( this.visual && this.collider ) {
      this.visual.visible = enableVisual;
      this.collider.visible = !enableVisual;
    }
  }

  enalbeAxes( enable=true, size=1 ) {

    if( this.threeObject && !this.axesHelper ) {

      this.axesHelper = new AxesHelper( size );

    }

    this.threeObject.add( this.axesHelper );
    this.axesHelper.visible = enable;

  }

  enableMaterial( enable=true ) {
    
    if( !this.visual ) return;

    this.visual.material = enable ? this.materials : null;

  }

  enableShadow( enable ) {

    if( this.visual ) {
      this.visual.receiveShadow = enable;
      this.visual.castShadow = enable;
    }

    if( this.collider ) {
      this.collider.receiveShadow = enable;
      this.collider.castShadow = enable;
    }

  }

  setVisulMaterial( material ) {

    if( this.visual ) {
      this.visual.material = material;
    }

  }

/******* ******* ******* ******* ******* ******* *******/
/******* 更新与资源释放 *******/
/******* ******* ******* ******* ******* ******* *******/

  update() {

    if( this.physicsBody ) {

      let body = this.physicsBody;

      if( this.isA3ObjectGroup && this.enableBaseShape ){
        body = this.physicsBody[0];
      }
    
      const ms = body.getMotionState();

      if ( ms ) {

        ms.getWorldTransform( worldTransform );
        const p = worldTransform.getOrigin();
        const q = worldTransform.getRotation();

        this.threeObject.position.set( p.x(), p.y(), p.z() );
        this.threeObject.quaternion.set( q.x(), q.y(), q.z(), q.w() );

        this.position.set( p.x(), p.y(), p.z() );
        this.quaternion.set( q.x(), q.y(), q.z(), q.w() );

      }
      
    }
    // console.log( this.position );

  }

  dispose() {

    if( this.visual ) {
      if( this.visual.material ){
        this.visual.material.dispose();
      }
      if( this.visual.geometry ) {
        this.visual.geometry.dispose();
      }
    }

    if( this.collider ) {
      if( this.collider.material ){
        this.collider.material.dispose();
      }
      if( this.collider.geometry ) {
        this.collider.geometry.dispose();
      }
    }

    // todo: 释放物理模型资源

  }

/******* ******* ******* ******* ******* ******* *******/
/******* 导入导出 *******/
/******* ******* ******* ******* ******* ******* *******/

  store() {

    const status = {
      position: this.position.clone(),
      quaternion: this.quaternion.clone(),
      linearVelocity : this.linearVelocity.clone(),
      angularVelocity: this.angularVelocity.clone(),
    }

    if( this.hasUserTransform ) {
      status["userPosition"] = this.userPosition.clone();
      status["userQuaternion"] = this.userQuaternion.clone();
    }

    this.lastestStatus = status;

  }

  reset() {

    if( this.lastestStatus ) {
      this.updateWorldTransform( this.lastestStatus.position, this.lastestStatus.quaternion );

      if( this.hasUserTransform ) {
        this.updateUserTransform( this.lastestStatus.userPosition, this.lastestStatus.userQuaternion );
      }

      this.setLinearVelocity( this.lastestStatus.linearVelocity );
      this.setAngularVelocity( this.lastestStatus.angularVelocity );
    }
    
  }  

  toJson() {

    const data = {

      uuid: this.uuid,
      name: this.name,
      type: this.type,
      meshType: this.meshType,

      isA3ObjectGroup: this.isA3ObjectGroup,

      position: this.position.toArray(),
      rotation: new Euler().setFromQuaternion(this.quaternion).toArray(),

      userPosition: this.userPosition.toArray(),
      userRotation: new Euler().setFromQuaternion(this.userQuaternion).toArray(),
      hasUserTransform: this.hasUserTransform,

      width: this.width,
      height: this.height,
      depth: this.depth,
      color: [ this.color.r, this.color.g, this.color.b ],
      
      mass: this.mass,
      inertia: this.inertia.toArray(),
      friction: this.friction,
      restitution: this.restitution,

      // linearVelocity : this.linearVelocity.toArray(),
      // angularVelocity: this.angularVelocity.toArray(),
    }

    data["material"] = mtlToJSON( this.material );
    data["materials"] = this.materials.map((mtl)=>mtlToJSON(mtl));

    // console.log( data );
    return data;

  }

  static fromJson( data, obj ) {

    if( obj === null || obj === undefined ) {
      obj = new A3Object( data.width, data.height, data.depth, data.meshType );
    }

    obj.uuid = data.uuid;
    obj.name = data.name;
    obj.type = data.type;

    obj.mass = data.mass;
    obj.friction = data.friction;
    obj.restitution = data.restitution;

    obj.setPosition( data.position[0], data.position[1], data.position[2] );
    obj.setRotation( data.rotation[0], data.rotation[1], data.rotation[2] );
    obj.inertia.set( data.inertia[0], data.inertia[1], data.inertia[2] );

    obj.setUserPosition( data.userPosition[0], data.userPosition[1], data.userPosition[2] );
    obj.setUserRotation( data.userRotation[0], data.userRotation[1], data.userRotation[2] );
    obj.hasUserTransform = data.hasUserTransform;

    obj.setColor( data.color[0], data.color[1], data.color[2] ) ;

    for( var i = 0, il = data.materials.length; i < il; i++ ) {
      obj.materials[i] = mtlFromJSON( data.materials[i] );
    }

    if( data.material ) {
      obj.material = mtlFromJSON( data.material );
    }

    return obj;

  }

/******* ******* ******* ******* ******* ******* *******/
/******* 动作相关 *******/
/******* ******* ******* ******* ******* ******* *******/

  debugAction(args) {

    console.log(`Actor[${this.uuid}] is working...`);
    for( var i = 0, il = args.length; i < il; i++ ){
      console.log(args[i]);
    }

  }

  addAction( action, shortcut, callback ) {
    
    const newAction = {

      name: action,
      shortcut: shortcut,
      callback: callback,
      enable: true,

    }

    this.actionMap.set( action, newAction );

  }

  disableAction( action ) {
    
    if( this.actionMap.has( action ) ) {
      const oldAction = this.actionMap.get( action );
      oldAction.enable = false;
      this.actionMap.set( action, oldAction );
    }

  }

  getActions() {
    
    return Object.keys( this.actionMap );

  }

  setActionShortcut( action, shortcut ) {

    if( this.actionMap.has( action ) ) {
      const oldAction = this.actionMap.get( action );
      oldAction.shortcut = shortcut;
      this.actionMap.set( action, oldAction );
    }

  }

  getActionShortcut( action ) {

    if( this.actionMap.has( action ) ) {
      return this.actionMap.get( action ).shortcut;
    }

    return null;

  }

  updateAction( action, callback ) {

    if( this.actionMap.has( action ) ) {

      const oldAction = this.actionMap.get( action );
      oldAction.callback = callback;
      this.actionMap.set( action, oldAction );
    }

  }

  doAction( action, ...args ) {
    
    if( this.actionMap.has( action ) ) {
      
      const tAction = this.actionMap.get( action );
      
      if( tAction.enable ) {
        // console.log(`Do action:${action}...`);
        tAction.callback( args );
      }

    }

  }

}

export default A3Object;